/*******************************************************************************
* Copyright (c) 2010 Composent, Inc. and others. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   Composent, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.internal.remoteservice.apt.java6;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;

import org.eclipse.ecf.internal.remoteservice.apt.java6.JavaFormatter.AccessSpecifier;
import org.eclipse.ecf.remoteservice.AsyncService;
import org.eclipse.ecf.remoteservice.IAsyncRemoteServiceProxy;

@SupportedAnnotationTypes({ "org.eclipse.ecf.remoteservice.AsyncService" }) 
public class AsyncAnnotationProcessor extends AbstractProcessor {

	private static final String FUTURE_PACKAGE_IMPORT = org.eclipse.equinox.concurrent.future.IFuture.class.getName();
	private static final String REMOTESERVICE_PACKAGE_IMPORT = org.eclipse.ecf.remoteservice.IAsyncCallback.class.getName();

	public AsyncAnnotationProcessor() {
		super();
	}
	
	public boolean process(Set<? extends TypeElement> annotations,
			RoundEnvironment roundEnv) {
		ProcessingEnvironment env = super.processingEnv;
		Filer filer = env.getFiler();
		Set<TypeElement> annotatedDecls = (Set<TypeElement>) roundEnv.getElementsAnnotatedWith(AsyncService.class);
		for(Iterator<TypeElement> i = annotatedDecls.iterator(); i.hasNext(); ) {
			writeAsyncType(filer,i.next());
		}
		return true;
	}

	private void writeAsyncType(Filer filer, TypeElement te) {
		String qualifiedAsyncName = te.getQualifiedName().toString()+IAsyncRemoteServiceProxy.ASYNC_INTERFACE_SUFFIX;
		String simpleAsyncName = te.getSimpleName().toString()+IAsyncRemoteServiceProxy.ASYNC_INTERFACE_SUFFIX;
		int lastDot = qualifiedAsyncName.lastIndexOf(".");
		String packageAsyncName = null;
		if (lastDot > 0) {
			packageAsyncName = qualifiedAsyncName.substring(0,lastDot);
		}
		JavaFileObject javaFile = null;
		try {
			javaFile = filer.createSourceFile(qualifiedAsyncName, (Element[]) null);
			Writer writer = javaFile.openWriter();
			PrintWriter pw = new PrintWriter(writer);
			JavaFormatter formatter = new JavaFormatter(pw);
			if (packageAsyncName != null) {
				formatter.printPackage(packageAsyncName);
				pw.println();
			}
			formatter.printImport(FUTURE_PACKAGE_IMPORT);
			formatter.printImport(REMOTESERVICE_PACKAGE_IMPORT);
			pw.println();
			formatter.printText("@SuppressWarnings(\"restriction\")");
			formatter.openInterface(AccessSpecifier.PUBLIC, simpleAsyncName, new String[] { IAsyncRemoteServiceProxy.class.getName() });
			pw.println();
			writeAsyncMethods(filer, te, (List<ExecutableElement>) te.getEnclosedElements(), formatter);
			pw.println();
			formatter.closeElement();
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void writeAsyncMethods(Filer filer, TypeElement te,
			List<ExecutableElement> list, JavaFormatter formatter) {
		for(Iterator<ExecutableElement> i = list.iterator(); i.hasNext(); ) {
		   ExecutableElement elem = i.next();
		   writeAsyncMethodCallback(elem, formatter);
		   writeAsyncMethodFuture(elem, formatter);
		}
	}

	private void writeAsyncMethodCallback(ExecutableElement methodDecl,
			JavaFormatter formatter) {
		   String resultType = getResultTypeForExecutableElement(methodDecl);
		   List<VariableElement> methodParams = (List<VariableElement>) methodDecl.getParameters();
		   List ptypes = new ArrayList();
		   List ps = new ArrayList();
		   for(Iterator<VariableElement> i=methodParams.iterator(); i.hasNext(); ) {
			   VariableElement m = i.next();
			   TypeMirror tm = m.asType();
			   ptypes.add(tm.toString());
			   ps.add(m.getSimpleName().toString());
		   }
		   final String asyncCallbackClassname = "IAsyncCallback";
		   if (resultType == null) {
			   ptypes.add(asyncCallbackClassname+"<Void>");
		   } else {
			   ptypes.add(asyncCallbackClassname+"<"+resultType+">");
		   }
		   ps.add("callback");
		   formatter.openMethod(false, AccessSpecifier.PUBLIC, null, methodDecl.getSimpleName()+IAsyncRemoteServiceProxy.ASYNC_METHOD_SUFFIX, (String[]) ptypes.toArray(new String[] {}), (String[]) ps.toArray(new String[] {}), false);
	}

	private String getBoxedTypeNameForTypeMirror(TypeMirror resultType) {
		if (resultType == null) return null;
		if (resultType instanceof ArrayType) {
			ArrayType at = (ArrayType) resultType;
			String boxedType = getBoxedTypeNameForTypeMirror(at.getComponentType());
			if (boxedType == null) return null;
			return boxedType+"[]";
		}
		TypeKind type = resultType.getKind();
		if (type == null) return null;
        if (type.equals(TypeKind.BOOLEAN)) {
			return "Boolean";
		} else if (type.equals(TypeKind.BYTE)) {
			return "Byte";
		} else if (type.equals(TypeKind.CHAR)) {
			return "Character";
		} else if (type.equals(TypeKind.DOUBLE)) {
			return "Double";
		} else if (type.equals(TypeKind.FLOAT)) {
			return "Float";
		} else if (type.equals(TypeKind.INT)) {
			return "Integer";
		} else if (type.equals(TypeKind.LONG)) {
			return "Long";
		} else if (type.equals(TypeKind.SHORT)) {
			return "Short";
		} else if (type.equals(TypeKind.VOID)) {
			return "Void";
		} else if (type.equals(TypeKind.DECLARED)) {
			return resultType.toString();
		}
        return null;
	}
	private String getResultTypeForExecutableElement(ExecutableElement methodDecl) {
		if (methodDecl == null) return null;
		TypeMirror returnType = methodDecl.getReturnType();
		if (returnType == null) return null;
		return getBoxedTypeNameForTypeMirror(returnType);
	}
	
	private void writeAsyncMethodFuture(ExecutableElement methodDecl,
			JavaFormatter formatter) {
		   List<VariableElement> methodParams = (List<VariableElement>) methodDecl.getParameters();
		   List ptypes = new ArrayList();
		   List ps = new ArrayList();
		   for(Iterator<VariableElement> i=methodParams.iterator(); i.hasNext(); ) {
			   VariableElement m = i.next();
			   TypeMirror tm = m.asType();
			   ptypes.add(tm.toString());
			   ps.add(m.getSimpleName().toString());
		   }
		   formatter.printText("@SuppressWarnings(\"rawtypes\")");
		   formatter.openMethod(false, AccessSpecifier.PUBLIC, "IFuture", methodDecl.getSimpleName()+IAsyncRemoteServiceProxy.ASYNC_METHOD_SUFFIX, (String[]) ptypes.toArray(new String[] {}), (String[]) ps.toArray(new String[] {}), false);
	}

}
